import pandas as pd
import statsmodels.api as sm
import settings


class Strategy:

    def _BBI_strategy(self):
        """
        用BBI-多空指数 对多只股票预期收益进行计算
        :return: 索引为日期的字典
        """
        new_dict = self.copy()
        for key, value in new_dict.items():
            new_dict[key] = Strategy._BBI_operation(value)
        return new_dict

    def _BOLL_strategy(self):
        """
        用BOLL-布林策略 对多只股票预期收益进行计算
        :return: 索引为日期的字典
        """
        new_dict = self.copy()
        for key, value in new_dict.items():
            new_dict[key] = Strategy._Boll_operation(value)
        return new_dict

    def _PSY_strategy(self):
        """
        用PSY-心理指标 对多只股票预期收益进行计算
        :return: 索引为日期的字典
        """
        new_dict = self.copy()
        for key, value in new_dict.items():
            new_dict[key] = Strategy._PSY_operation(value)
        return new_dict

    def _RSI_strategy(self):
        """
        用RSI-相对强弱指标 对多只股票预期收益进行计算
        :return: 索引为日期的字典
        """
        new_dict = self.copy()
        for key, value in new_dict.items():
            new_dict[key] = Strategy._RSI_operation(value)
        return new_dict

    def _BBI_operation(self):
        """
        交易策略简介：BBI是一种将不同日数移动平均线加权平均之后的综合指标。
        买卖判断依据：收盘价格上行突破BBI多空指标，买入；反之，卖出
        计算使用的指标：BBI、收盘价 close
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self.copy(deep=True)
        new_df = pd.concat([new_df, pd.DataFrame(columns=[settings.TRADE_FLAG])], sort=False)
        for index, row in new_df.iterrows():
            if not row['BBI'] <= row['close']:
                # 价格高于BBI，上行，买入
                row[settings.TRADE_FLAG] = True
            else:
                # 价格低于BBI，下行，卖出
                row[settings.TRADE_FLAG] = False
            new_df.loc[index] = row
        return new_df

    def _Boll_operation(self_data_frame):
        """
        交易策略简介：boll_down 为下轨线（布林线）指标、boll_up 上轨线（布林线）指标，二者形成一条通道
        买卖判断依据：当上升穿越布林线上轨线时，回档机率大，卖出；股价下跌穿越布林线下轨线时，反弹机率大，买入
        计算使用的指标：boll_down 下轨线指标、boll_up上轨线指标、high 最高价、low 最低价
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self_data_frame.copy(deep=True)
        new_df = pd.concat([new_df, pd.DataFrame(columns=[settings.TRADE_FLAG])], sort=False)
        for index, row in new_df.iterrows():
            if row['low'] < row['boll_down']:
                # 当天股票最低价跌破布林线下轨，为买入信号
                row[settings.TRADE_FLAG] = True
            if row['high'] > row['boll_up']:
                # 当天股票最高价突破布林线上轨，为卖出信号
                row[settings.TRADE_FLAG] = False
            new_df.loc[index] = row
        return new_df

    def _PSY_operation(self_data_frame):
        """
        交易策略简介：PSY是心理线指标，研究投资者对股市涨跌产生心理波动的情绪指标
        买卖判断依据：PSY>75，形成Ｍ头时，股价容易遭遇压力； PSY<25，形成Ｗ底时，股价容易获得支撑
        计算使用的指标：PSY
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self_data_frame.copy(deep=True)
        new_df = pd.concat([new_df, pd.DataFrame(columns=[settings.TRADE_FLAG])], sort=False)
        for index, row in new_df.iterrows():
            if row['PSY'] < 25:
                #PSY低于25，上行，买入
                row[settings.TRADE_FLAG] = True
            elif row['PSY'] > 75:
                #PSY大于75，卖出
                row[settings.TRADE_FLAG] = False
            new_df.loc[index] = row
        return new_df

    def _RSI_operation(self_data_frame):
        """
        交易策略简介：RSI为相对强弱指标，是一个信号指标。
        买卖判断依据：当RSI小于30时，是买入的信号；反之，卖出
        计算使用的指标：RSI
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self_data_frame.copy(deep=True)
        new_df = pd.concat([new_df, pd.DataFrame(columns=[settings.TRADE_FLAG])], sort=False)
        for index, row in new_df.iterrows():
            if row['RSI'] < 30:
                # RSI低于30，上行，买入
                row[settings.TRADE_FLAG] = True
            else:
                # RSI低于30，卖出
                row[settings.TRADE_FLAG] = False
            new_df.loc[index] = row
        return new_df

    def MA_stategy(self_data_frame):
        """
        交易策略简介：MA-均线策略具有趋势的特性，根据价格和平均线的关系来提示买入卖出
        买卖判断依据：股价高于平均线，视为强势，且平均线向上涨升，具有助涨力道，买入；
                   股价低于平均线，视为弱势，且平均线向下跌降，具有助跌力道，卖出
        计算使用的指标：MA值及其滞后一期值M、low 最低价、high 最高价
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self_data_frame.copy(deep=True)
        new_df = pd.concat([new_df, pd.DataFrame(columns=['MA_op'])], sort=False)
        for index, row in new_df.iterrows():
            # 当天最低价高于均线，表示强势，且当日MA值高于前一交易日MA值，表示该上涨趋势可延续，买入
            if row['low'] > row['MA'] and row['MA'] > row['MA'.shift(1)]:
                row['MA_op'] = True
            # 当天最高价低于均线，表示弱势，且当日MA值低于前一交易日MA值，表示该下跌趋势可延续，卖出
            if row['high'] < row['MA'] and row['MA'] < row['MA'.shift(1)]:
                row['MA_op'] = False
            new_df.loc[index] = row
        return new_df

    def BIAS_stategy(self_data_frame):
        """
        交易策略简介：BIAS策略，根据市场指数或收盘价与某条移动平均价格之间的差距，用来预警金价的暴涨和暴跌引发的行情逆转
        买卖判断依据：当股价的正乖离扩大到一定极限时，股价会产生向下拉回的作用力，卖出；
                   当股价的负乖离扩大到一定极限时，股价会产生向上拉升的作用力，买入。
                   本策略中，选定-3%是买进时机，+3%是卖出时机
        计算使用的指标：5日乖离率=（当日收盘价-5日内移动平均价）/5日内移动平均价 * 100%
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self_data_frame.copy(deep=True)
        new_df = pd.concat([new_df, pd.DataFrame(columns=['BIAS_op'])], sort=False)
        # 计算5日移动平均价
        new_df['ma5'] = pd.rolling_mean(new_df['close'], 5)
        # 计算5日乖离率
        new_df['BIAS'] = (new_df['close'] - new_df['BIAS']) / new_df['BIAS'] * 100
        for index, row in new_df.iterrows():
            if row['BIAS'] > 3.5:
                # 乖离率大于3.5%，上行，买入
                row[settings.TRADE_FLAG] = True
            else:
                row[settings.TRADE_FLAG] = False
            new_df.loc[index] = row
        return new_df

    def MTM_strategy(self_data_frame):
        """
        交易策略简介：MTM策略，指标反映股价在波动过程中各种加速、减速、惯性作用以及股价由静到动或由动转静的现象
        买卖判断依据：MTM从下向上突破MTMMA，买入信号； MTM从上向下跌破MTMMA，卖出信号
        计算使用的指标：MTM，对MTM取6日移动平均-MTMMA6
        :return: dataframe(index:股票代码，column:因子 + TRADE_FLAG)
        """
        new_df = self_data_frame.copy(deep = True)
        new_df = pd.concat([new_df, pd.DataFrame(columns = ['MTM_op'])], sort = False)
        #计算6日MTMMA线
        new_df['MTMMA6'] = pd.rolling_mean(new_df['MTM'],6)

        for index, row in new_df.iterrows():
            if row['MTM'] >  row['MTMMA6']:
                # MTM从下向上突破MTMMA，买入信号
                row['MTM_op'] = True
            else:
                row['MTM_op'] = False
            new_df.loc[index] = row
        return new_df



    @staticmethod
    def _regression(data_frame):
        """
        Step1：
        对选定的四个因子进行多元回归
        - 自变量对应为：动量效应momentum，反转效应reverse，总市值size，账面市值比BM
        - 因变量对应为：超额收益率excess_return
        - 首先进行相关性分析，而后进行OLS多元回归
        - 回归的目的：得到各因子系数与拟合的股票评分模型
        :return: dict{key:因子， value:经OLS回归得到的因子的系数}
        """
        df = data_frame
        x = df[['momentum', 'reverse','size', 'BM']]
        # 相关性分析
        cor = x.corr()
        # 进行OLS回归，在自变量中加入常数项
        x = sm.add_constant(x)
        y = df[['excess_return']]
        regression_model = sm.OLS(y.astype(float), x.astype(float)).fit()

        #取出回归系数到list中
        coefficient_series = regression_model.params
        coefficient = {}
        for index,value in coefficient_series.items():
            coefficient[index] = value

        return coefficient


    @staticmethod
    def _score(data_frame):
        """
        Step2：
        - 根据Step1得到的回归模型，对股票池中的若干股票进行评分
        - 根据评分结果进行倒序排序
        - 取出排名在前10%的股票名称，存入列表
        :return: list[选取出的排名10%股票代码]
        """
        df = data_frame
        coefficient = Strategy._regression(df)
        df['score'] = ''
        for index, row in df.iterrows():
            #根据回归系数，计算每个股票的得分
            df.loc[index, 'score'] = row['momentum'] * coefficient['momentum'] + row['reverse'] * coefficient['reverse'] + row['size'] * coefficient['size'] + row['BM'] * coefficient['BM'] + coefficient['const']

        #根据分数进行股票排序
        df = df.sort_values(by = 'score', ascending = False)
        # 将股票名取出到list
        stock_list = df.index.values.tolist()
        #取出前10%的股票
        target_stock_list = stock_list[:settings.HOLDING_NUMS]

        return target_stock_list


    def _multiple_factor_strategy(self):
        """
        Step3：
        - 如果股票池中股票，在Step2排序得出的股票列表中，则买入，否则不进行操作或卖出
        - 预备操作：将上周收盘价加入到本周的dataframe中，以便回测
        :return: dict{key:时间； value:dataframe(index:股票代码, value:收盘价、因子、买卖判断)}
        """
        data_dict = self.copy()
        date_list = sorted(data_dict.keys())
        first_df = data_dict[date_list[0]]
        target_stock_list = []
        pre_close = first_df['close']
        for date_string in date_list:
            df = data_dict[date_string]
            df[settings.TRADE_FLAG] = ''
            df['open'] = ''
            df['open'] = pre_close
            pre_close = df['close']
            target_stock_list = Strategy._score(df)
            for index, row in df.iterrows():
                #若该行的股票在之前取出的list中，则买入
                if index in target_stock_list:
                    df.loc[index, settings.TRADE_FLAG] = True
                #否则，卖出
                else:
                    df.loc[index, settings.TRADE_FLAG] = False

            data_dict[date_string] = df
        return data_dict

    def Strategy_Not_Found(self):
        print("no such strategy")

    strategy_dict = {
        'BBI': _BBI_strategy,
        'BOLL': _BOLL_strategy,
        'PSY': _PSY_strategy,
        'RSI': _RSI_strategy,
        'MUL': _multiple_factor_strategy
    }

    @staticmethod
    def Select_Strategy(strategy_name, stock_data):
        return Strategy.strategy_dict.get(strategy_name, Strategy.Strategy_Not_Found)(stock_data)
